/* Modified by Yen-Ju Chen */
/*
 * hpricot_scan.rl
 *
 * $Author: why $
 * $Date: 2006-05-08 22:03:50 -0600 (Mon, 08 May 2006) $
 *
 * Copyright (C) 2006 why the lucky stiff
 */

#import <Foundation/Foundation.h>
#import "GNUstep.h"
#import "HParser.h"

static char *sym_xmldecl = "xmldecl";
static char *sym_doctype = "doctype";
static char *sym_procins = "procins";
static char *sym_stag = "stag";
static char *sym_etag = "etag";
static char *sym_emptytag = "emptytag";
static char *sym_comment = "comment";
static char *sym_cdata = "cdata";
static char *sym_text = "text";

#define ELE(N) \
  if (tokend > tokstart || text == 1) { \
    char *raw_string = NULL; \
    ele_open = 0; text = 0; \
    if (tokstart != 0 && sym_##N != sym_cdata && sym_##N != sym_text && sym_##N != sym_procins && sym_##N != sym_comment) { \
      raw_string = malloc(sizeof(char)*(tokend-tokstart+1)); \
      memset(raw_string, 0, tokend-tokstart+1); \
      memcpy(raw_string, tokstart, tokend-tokstart); \
    } \
	[self _yieldTokens :sym_##N :tag :attr :raw_string]; \
  }

#define SET(N, E) \
  if (N != NULL) free(N); \
  if (mark_##N == NULL || E == mark_##N) \
  { \
    N = malloc(sizeof(char)); \
    memset(N, 0, 1); \
  } \
  else if (E > mark_##N) \
  { \
    N = malloc(sizeof(char) * (E - mark_##N + 1)); \
    memset(N, 0, (E - mark_##N + 1)); \
    memcpy(N, mark_##N, E - mark_##N); \
  }

#define CAT(N, E) \
  if (N == NULL) \
    { SET(N, E); } \
  else \
    { \
      int new_size = strlen(N) + E - mark_##N + 1; \
      N = realloc(N, new_size); \
      char *xptr = N + strlen(N); \
      memcpy(xptr, mark_##N, E - mark_##N); \
      xptr += E - mark_##N; \
      xptr = '\0'; \
    }

#define SLIDE(N) if ( mark_##N > tokstart ) mark_##N = buf + (mark_##N - tokstart);

#define ATTR(K, V) \
    if (K != NULL) { \
      if (attr == nil) { \
        attr = [[[NSMutableDictionary alloc] init] autorelease]; \
      } \
      [attr setObject: [NSString stringWithCString: V encoding: NSUTF8StringEncoding] \
               forKey: [NSString stringWithCString: K encoding: NSUTF8StringEncoding]]; \
    }
     
#define TEXT_PASS() \
    if (text == 0) \
    { \
      if (ele_open == 1) { \
        ele_open = 0; \
        if (tokstart > 0) { \
          mark_tag = tokstart; \
        } \
      } else { \
        mark_tag = p; \
      } \
      attr = nil; \
      tag = NULL; \
      text = 1; \
    }

#define EBLK(N, T) CAT(tag, p - T + 1); ELE(N);

%%{
  machine hpricot_scan;

  action newEle {
    if (text == 1) {
      CAT(tag, p);
      ELE(text);
      text = 0;
    }
    attr = nil;
    tag = NULL;
    mark_tag = NULL;
    ele_open = 1;
  }

  action _tag { mark_tag = p; }
  action _aval { mark_aval = p; }
  action _akey { mark_akey = p; }
  action tag { SET(tag, p); }
  action tagc { SET(tag, p-1); }
  action aval { SET(aval, p); }
  action aunq { 
    if (*(p-1) == '"' || *(p-1) == '\'') { SET(aval, p-1); }
    else { SET(aval, p); }
  }
  action akey { SET(akey, p); }
  action xmlver { SET(aval, p); ATTR("version", aval); }
  action xmlenc { SET(aval, p); ATTR("encoding", aval); }
  action xmlsd  { SET(aval, p); ATTR("standalone", aval); }
  action pubid  { SET(aval, p); ATTR("public_id", aval); }
  action sysid  { SET(aval, p); ATTR("system_id", aval); }

  action new_attr { 
    akey = NULL;
    aval = NULL;
    mark_akey = NULL;
    mark_aval = NULL;
  }

  action save_attr { 
    ATTR(akey, aval);
  }

  #
  # HTML tokens
  # (a blatant rip from HTree)
  #
  newline = '\n' @{curline += 1;} ;
# qtext = '"' ( '\"' | [^\n"] )* '"' | "'" ( "\\'" | [^\n'] )* "'" ; 
  NameChar = [\-A-Za-z0-9._:?] ;
  Name = [A-Za-z_:] NameChar* ;
  StartComment = "<!--" ;
  EndComment = "-->" ;
  StartCdata = "<![CDATA[" ;
  EndCdata = "]]>" ;

  NameCap = Name >_tag %tag;
  NameAttr = NameChar+ >_akey %akey ;
  Q1Char = ( "\\\'" | [^'] ) ; 
  Q1Attr = Q1Char* >_aval %aval ; 
  Q2Char = ( "\\\"" | [^"] ) ; 
  Q2Attr = Q2Char* >_aval %aval ;
  UnqAttr = ( space >_aval | [^ \t\r\n<>"'] >_aval [^ \t\r\n<>]* %aunq ) ;
  Nmtoken = NameChar+ >_akey %akey ;

  Attr =  NameAttr space* "=" space* ('"' Q2Attr '"' | "'" Q1Attr "'" | UnqAttr space+ ) space* ;
  AttrEnd = ( NameAttr space* "=" space* UnqAttr? | Nmtoken >new_attr %save_attr ) ;
  AttrSet = ( Attr >new_attr %save_attr | Nmtoken >new_attr space+ %save_attr ) ;
  StartTag = "<" NameCap space+ AttrSet* (AttrEnd >new_attr %save_attr)? ">" | "<" NameCap ">";
  EmptyTag = "<" NameCap space+ AttrSet* (AttrEnd >new_attr %save_attr)? "/>" | "<" NameCap "/>" ;

  EndTag = "</" NameCap space* ">" ;
  XmlVersionNum = [a-zA-Z0-9_.:\-]+ >_aval %xmlver ;
  XmlVersionInfo = space+ "version" space* "=" space* ("'" XmlVersionNum "'" | '"' XmlVersionNum '"' ) ;
  XmlEncName = [A-Za-z] >_aval [A-Za-z0-9._\-]* %xmlenc ;
  XmlEncodingDecl = space+ "encoding" space* "=" space* ("'" XmlEncName "'" | '"' XmlEncName '"' ) ;
  XmlYesNo = ("yes" | "no") >_aval %xmlsd ;
  XmlSDDecl = space+ "standalone" space* "=" space* ("'" XmlYesNo "'" | '"' XmlYesNo '"') ;
  XmlDecl = "<?xml" XmlVersionInfo XmlEncodingDecl? XmlSDDecl? space* "?"? ">" ;

  SystemLiteral = '"' [^"]* >_aval %sysid '"' | "'" [^']* >_aval %sysid "'" ;
  PubidLiteral = '"' [\t a-zA-Z0-9\-'()+,./:=?;!*\#@$_%]*  >_aval %pubid '"' |
    "'" [\t a-zA-Z0-9\-'()+,./:=?;!*\#@$_%]* >_aval %pubid "'" ;
  ExternalID = ( "SYSTEM" | "PUBLIC" space+ PubidLiteral ) (space+ SystemLiteral)? ;
  DocType = "<!DOCTYPE" space+ NameCap (space+ ExternalID)? space* ("[" [^\]]* "]" space*)? ">" ;
  StartXmlProcIns = "<?" Name >{ TEXT_PASS(); } space+ ;
  EndXmlProcIns = "?"? ">" ;

  html_comment := |*
    EndComment @{ EBLK(comment, 3); fgoto main; };
    any | newline { TEXT_PASS(); };
  *|;

  html_cdata := |*
    EndCdata @{ EBLK(cdata, 3); fgoto main; };
    any | newline { TEXT_PASS(); };
  *|;

  html_procins := |*
    EndXmlProcIns @{ EBLK(procins, 2); fgoto main; };
    any | newline { TEXT_PASS(); };
  *|;

  main := |*
    XmlDecl >newEle { ELE(xmldecl); };
    DocType >newEle { ELE(doctype); };
    StartXmlProcIns >newEle { fgoto html_procins; };
    StartTag >newEle { ELE(stag); };
    EndTag >newEle { ELE(etag); };
    EmptyTag >newEle { ELE(emptytag); };
    StartComment >newEle { fgoto html_comment; };
    StartCdata >newEle { fgoto html_cdata; };
    any | newline { TEXT_PASS(); };
  *|;
}%%

%% write data nofinal;

#define BUFSIZE 16384

@implementation HParser
- (id) init
{
	self = [super init];
	usePath = NO;
	path = [[NSString alloc] init]; 
	responseMask = HPNoResponseMask;
	return self;
}

- (id) initWithContentsOfFile: (NSString *) file
{
	self = [self init];
	/* Check file existence */
	NSFileManager *fm = [NSFileManager defaultManager];
	BOOL isDir = NO;
	if (([fm fileExistsAtPath: file isDirectory: &isDir] == NO) ||
	    (isDir == YES))
	{
		DESTROY(path);
		[self dealloc];
		return nil;
	}
	contents = [[NSString alloc] initWithContentsOfFile: file 
	                             encoding: NSUTF8StringEncoding error: NULL];
	return self;
}

- (void) dealloc
{
	DESTROY(contents);
	DESTROY(path);
	DESTROY(delegate);
	[super dealloc];
}

- (void) setDelegate: (id) d
{
	ASSIGN(delegate, d);
	if ([delegate respondsToSelector: @selector(parser:foundStartElement:attributes:path:)])
	{
		responseMask |= HPStartElementResponseMask;
	}
	if ([delegate respondsToSelector: @selector(parser:foundEndElement:attributes:path:)])
	{
		responseMask |= HPEndElementResponseMask;
	}
	if ([delegate respondsToSelector: @selector(parser:foundText:attributes:path:)])
	{
		responseMask |= HPTextResponseMask;
	}
}

- (id) delegate
{
	return delegate;
}

- (void) setUsePath: (BOOL) flag
{
	usePath = flag;
}

- (BOOL) usePath
{
	return usePath;
}

- (void) _yieldTokens: (char *) sym : (char *) tag 
                     : (NSDictionary *) attr : (char *) raw
{
	if (strcmp(sym, sym_text) == 0) 
	{
		free(raw);
		raw = strdup(tag);
	}
	
	if (strcmp(sym, sym_xmldecl) == 0)
	{
	}
	else if (strcmp(sym, sym_doctype) == 0)
	{
	}
	else if (strcmp(sym, sym_procins) == 0)
	{
	}
	else if (strcmp(sym, sym_stag) == 0)
	{
		NSString *ns_tag = [NSString stringWithUTF8String: tag];
		ASSIGN(path, [path stringByAppendingPathComponent: ns_tag]);
		if (responseMask & HPStartElementResponseMask)
		{
			[delegate parser: self 
			          foundStartElement: ns_tag
			          attributes: attr
			          path: (usePath == YES) ? path : nil];
		}
	}
	else if (strcmp(sym, sym_etag) == 0)
	{
		NSString *ns_tag = [NSString stringWithUTF8String: tag];
		if (responseMask & HPEndElementResponseMask)
		{
			[delegate parser: self 
			          foundEndElement: ns_tag
			          attributes: attr
			          path: (usePath == YES) ? path : nil];
		}
		ASSIGN(path, [path stringByDeletingLastPathComponent]);
	}
	else if (strcmp(sym, sym_emptytag) == 0)
	{
		NSString *ns_tag = [NSString stringWithUTF8String: tag];
		ASSIGN(path, [path stringByAppendingPathComponent: ns_tag]);
		if (responseMask & HPStartElementResponseMask)
		{
			[delegate parser: self 
			          foundStartElement: ns_tag
			          attributes: attr
			          path: (usePath == YES) ? path : nil];
		}
		if (responseMask & HPEndElementResponseMask)
		{
			[delegate parser: self 
			          foundEndElement: ns_tag
			          attributes: attr
			          path: (usePath == YES) ? path : nil];
		}
		ASSIGN(path, [path stringByDeletingLastPathComponent]);
	}
	else if (strcmp(sym, sym_comment) == 0)
	{
	}
	else if (strcmp(sym, sym_cdata) == 0)
	{
	}
	else if (strcmp(sym, sym_text) == 0)
	{
		if (responseMask & HPTextResponseMask)
		{
			NSString *ns_tag = [NSString stringWithUTF8String: tag];
			ns_tag = [ns_tag stringByTrimmingCharactersInSet: [NSCharacterSet whitespaceAndNewlineCharacterSet]];
			if ([ns_tag length])
			{
				[delegate parser: self 
				          foundText: ns_tag
				          attributes: attr
				          path: (usePath == YES) ? path : nil];
			}
		}
	}
	else
	{
		NSLog(@"Unknown type: %s, %s, %@, %s", sym, tag, attr, raw);
	}

	free(raw); // created in ELE() macro
	free(tag); // probably created by SET() and CAT()
}

- (void) parse
{
	char *port = (char*)[contents UTF8String];
	int cs, act, have = 0, nread = 0, curline = 1, text = 0;
	char *tokstart = 0, *tokend = 0, *buf = NULL;

	NSMutableDictionary *attr = nil;
	char *tag = NULL;
	char *akey = NULL;
	char *aval = NULL;

 	char *mark_tag = 0, *mark_akey = 0, *mark_aval = 0;
	int done = 0, ele_open = 0, buffer_size = 0;

	buffer_size = BUFSIZE;
	buf = malloc(sizeof(char)*buffer_size);

	%% write init;
  
	while ( !done ) 
	{
		const char *str = NULL;
		char *p = buf + have, *pe;
		int len, space = buffer_size - have;

		if ( space == 0 ) 
		{
			/* We've used up the entire buffer storing an already-parsed token
			 * prefix that must be preserved.  Likely caused by super-long 
			 *attributes. See ticket #13. */
			printf("ran out of buffer space on element <%s>, starting on line %d\n", tag, curline);
		}

		int length = strlen(port);
		int max_len = (space < length - nread) ? space : length-nread;
		str = port+nread;
		memcpy( p, str, max_len);
		len = max_len;
		nread += len;

		/* If this is the last buffer, tack on an EOF. */
		if ( len < space ) 
		{
			p[len++] = 0;
			done = 1;
		}

		pe = p + len;
		%% write exec;
    
		if ( cs == hpricot_scan_error ) 
		{
			free(buf);
			if (tag != NULL)
			{
				printf("parse error on element <%s>, starting on line %d.\n", tag, curline);
			}
			else
			{
				printf("parse error on line %d.\n", curline);
			}
		}
    
		if ( done && ele_open )
		{
			ele_open = 0;
			if (tokstart > 0) 
			{
				mark_tag = tokstart;
				tokstart = 0;
				text = 1;
			}
		}

		if ( tokstart == 0 )
		{
			have = 0;
			/* text nodes have no tokstart because each byte is parsed alone */
			if ( mark_tag != NULL && text == 1 )
			{
				if (done)
				{
					if (mark_tag < p-1)
					{
						CAT(tag, p-1);
						ELE(text);
					}
				}
				else
				{
					CAT(tag, p);
				}
			}
			mark_tag = buf;
		}
		else
		{
			have = pe - tokstart;
			memmove( buf, tokstart, have );
			SLIDE(tag);
			SLIDE(akey);
			SLIDE(aval);
			tokend = buf + (tokend - tokstart);
			tokstart = buf;
		}
	}
	free(buf);
}
 
@end
